import json
import os.path
import platform
import socket
import time
from codecs import encode, decode

import yaml
from robot.api import logger

from Lib.zcm.dcc.config import dcc_cfg
from Lib.zcm.dcc.model import HDRItem
from Lib.zcm.dcc.utils import stripHdr, splitMsgAVPs, decodeAVP, create_ccr_message, create_DPR, create_CER, \
    dictCOMMANDcode2name, create_DWA, create_send_msg
from Lib.zcm.assistant import FwVariables
from Lib.constants import *
import uuid


class DccRequest(object):
    def __init__(self):
        self.host = dcc_cfg.env['DCCConnection']['Host']
        self.port = dcc_cfg.env['DCCConnection']['Port']
        self.is_connected = False
        self.is_login = False
        self.sock = None
        self.MSG_SIZE = 4096

    def __enter__(self):
        if not self.connect():
            raise BaseException(f"Can not connect dcc {self.host}:{self.port}")
        if not self.login():
            raise BaseException(f"Can not login dcc {self.host}:{self.port}")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()

    def connect(self):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((self.host, self.port))
            self.is_connected = True
            return True
        except BaseException as e:
            logger.error("[connect] Fail to connect to %s:%s. Error message: %s" % (self.host, self.port, str(e)))
            return False

    def login(self):
        if not self.is_connected:
            if not self.connect():
                return False
        logger.debug("[login] Login to DCC begin...")
        cer = create_CER()
        if len(cer) % 2 != 0:
            cer = cer + "e"
        self.sock.send(decode(cer, "hex"))
        msg = self.sock.recv(self.MSG_SIZE)
        H = HDRItem()
        stripHdr(H, encode(msg, "hex"))

        if msg:
            avps = splitMsgAVPs(H.msg)
            for avp in avps:
                logger.debug("[login] login_src: %s" % str(decodeAVP(avp)))
            logger.info("[login] Successfully login to DCC.")
            self.is_login = True
            return True
        else:
            logger.error("[login] Fail to login to DCC.")
            return False

    def disconnect(self):
        try:
            if self.is_login:
                dpr = create_DPR()
                self.sock.send(decode(dpr, "hex"))
                self.is_login = False
                logger.info("[disconnect] Send DPR: %s" % (dpr))
        except BaseException as e:
            logger.error("[disconnect] Process error!" + str(e))
        finally:
            if self.is_connected:
                self.sock.close()
                self.is_connected = False
                logger.info("[disconnect] Socket Closed.")

    def send_dcc_message(self, acc_nbr, service_type=None, cdr_attr=None):
        if not self.is_login:
            if not self.login():
                raise BaseException('Login failed!')
        logger.info('[step] Start SendDccMsg')
        messages = create_ccr_message(acc_nbr, service_type, cdr_attr)
        for msg in messages:
            logger.info("[do_send] send finish, msg: %s" % msg)
            send_msg = decode(msg.strip(), "hex")
            self.sock.send(send_msg)

    def send_dcc_message_with_response(self, acc_nbr, service_type=None, cdr_attr=None, wait_time_out=30):
        self.send_dcc_message(acc_nbr, service_type, cdr_attr)
        return self.rev_message(wait_time_out)

    def rev_message(self, timeout=30):
        try:
            is_response = False
            result_message = None
            end_time = time.time() + timeout
            while not is_response and time.time() < end_time:
                msg = self.sock.recv(self.MSG_SIZE)
                if msg:
                    REV = HDRItem()

                    stripHdr(REV, encode(msg, "hex"))
                    # avps = splitMsgAVPs(REV.msg)
                    cmd = dictCOMMANDcode2name(REV.flags, REV.cmd)
                    logger.info("[do_result] Receive: %s" % cmd)

                    if cmd == "Device-Watchdog Request":
                        logger.debug("[do_result] Receive DWR.")
                        dwa = create_DWA()
                        if len(dwa) % 2 != 0:
                            dwa = dwa + "e"
                        self.sock.send(decode(dwa, "hex"))
                        # self.send_queue.put(decode(dwa, "hex"))
                        logger.debug("[do_result] Send DWA: %s" % (dwa))
                    elif cmd == "Credit-Control Answer":
                        result_message = REV
                        is_response = True
                    else:
                        logger.error("[do_result] decode rev msg error %s" % str(cmd))
            return result_message
        except BaseException as e:
            logger.error("[do_rev] do_rev thread exit!  %s" % (str(e)))


class DccRequest2(DccRequest):
    def __init__(self, olc_link_name):
        super().__init__()
        self.host = FwVariables.get_olc_host(olc_link_name)
        self.port = int(FwVariables.get_olc_port(olc_link_name))


class DccOperate:
    def __init__(self):
        self.instance = dict()

    def __del__(self):
        for link_name in self.instance:
            obj = self.instance[link_name]
            obj.disconnect()

    def get_dcc_socket(self, link_name):
        if link_name in self.instance:
            return self.instance[link_name]
        dcc_request = DccRequest2(link_name)
        if not dcc_request.connect():
            raise BaseException(f"Can not connect dcc {dcc_request.host}:{dcc_request.port}")
        if not dcc_request.login():
            raise BaseException(f"Can not login dcc {dcc_request.host}:{dcc_request.port}")
        self.instance[link_name] = dcc_request
        return dcc_request

    def connect(self, olc_link_name):
        self.get_dcc_socket(olc_link_name)

    def get_avp_dict_map(self, olc_link_name, avp_type, request_dict):
        from Lib.zcm.dcc.model import AVPItem
        # if platform.system().lower() == 'windows':
        #     avpfile_root_path = "D:\\code\\gitea_cloudtest_library\\Case\\avpfile"
        # else:
        avpfile_root_path = os.path.join(os.getcwd() + os.sep + "Case",
                                             FwVariables.get_dcc_avpfile_path(olc_link_name))
        result = self.__get_avpfile_text(avp_type, avpfile_root_path)
        result = result[1:]
        avp_dict_map = dict()
        dict_avps_code_map = dict()
        # Service-Information单独处理
        special_dict = dict()
        service_information = "Service-Information"
        service_information_vendor_id = 10415
        special_dict[service_information] = {"service_information_vendor_id": service_information_vendor_id}
        service_key = "Service-Key"
        service_key_vendor_id = 0
        special_dict[service_key] = {"service_key_vendor_id": service_key_vendor_id}
        Calling_ellID_Or_SAI = "Calling-CellID-Or-SAI"
        Calling_ellID_Or_SAI_vendor_id = 81000
        special_dict[Calling_ellID_Or_SAI] = {"Calling_ellID_Or_SAI_vendor_id": Calling_ellID_Or_SAI_vendor_id}
        Calling_LAI = "Calling-LAI"
        Calling_LAI_vendor_id = 0
        special_dict[Calling_LAI] = {"Calling_LAI_vendor_id": Calling_LAI_vendor_id}
        Bearer_Capability = "Bearer-Capability"
        Bearer_Capability_vendor_id = 0
        special_dict[Bearer_Capability] = {"Bearer_Capability_vendor_id": Bearer_Capability_vendor_id}
        EventType_BCSM = "EventType-BCSM"
        EventType_BCSM_code = 20315
        special_dict[EventType_BCSM] = {"EventType_BCSM_code": EventType_BCSM_code}
        Call_Reference_Number = "Call-Reference-Number"
        Call_Reference_Number_vendor_id = 0
        special_dict[Call_Reference_Number] = {"Call_Reference_Number_vendor_id": Call_Reference_Number_vendor_id}
        if service_information in request_dict:
            if "CS-Information" in request_dict[service_information]:
                special_dict[service_information]["service_information_vendor_id"] = 0
                special_dict[Calling_ellID_Or_SAI]["Calling_ellID_Or_SAI_vendor_id"] = 0
            else:
                if "IN-Information" in request_dict[service_information]:
                    special_dict[service_key]["service_key_vendor_id"] = 81000
                    special_dict[Calling_LAI]["Calling_LAI_vendor_id"] = 81000
                    special_dict[Bearer_Capability]["Bearer_Capability_vendor_id"] = 81000
                    special_dict[Call_Reference_Number]["Call_Reference_Number_vendor_id"] = 81000
                elif "P2PSMS-Information" in request_dict[service_information]:
                    special_dict[EventType_BCSM]["EventType_BCSM_code"] = 30320
        for line in result:
            line = line.split()
            avpItem = AVPItem()
            avp_name = line[0]
            avp_name = self.remove_bracket(avp_name)
            avpItem.name = avp_name
            avpItem.code = int(line[2])
            avpItem.mandatory = "must" if line[8] == "M" else ""
            avpItem.type = AVP_TYPE_MAP.get(line[4], line[4])
            avpItem.vendor = int(line[3])
            if avpItem.name:
                # 注意name会重复
                if avpItem.name in special_dict:
                    special_value = special_dict[avpItem.name]
                    is_join = True
                    for key in special_value:
                        if key.endswith("vendor_id"):
                            if special_value[key] != avpItem.vendor:
                                is_join = False
                        if key.endswith("code"):
                            if special_value[key] != avpItem.code:
                                is_join = False
                    if is_join:
                        avp_dict_map[avpItem.name] = avpItem
                else:
                    avp_dict_map[avpItem.name] = avpItem
                # avp_dict_map[",".join([avpItem.name, str(avpItem.vendor) if avpItem.vendor != 0 else ""])] = avpItem
            dict_avps_code_map[
                ",".join([str(avpItem.code), str(avpItem.vendor) if avpItem.vendor != 0 else ""])] = avpItem
        return avp_dict_map, dict_avps_code_map

    def send_dcc_message(self, olc_link_name, avp_type, request_dict, check_dict):
        time_out = FwVariables.get_dcc_rec_time_out(olc_link_name)
        dcc_request = self.get_dcc_socket(olc_link_name)
        avp_dict_map, dict_avps_code_map = self.get_avp_dict_map(olc_link_name, avp_type, request_dict)
        # Service_Information = avp_dict_map.get("Service-Information")
        messages = create_send_msg(request_dict, avp_dict_map, avp_type)
        # # logger.info("[do_send] send finish, msg: %s" % messages)
        if len(messages) % 2 != 0:
            messages = messages + "e"
        send_msg = decode(messages, "hex")
        dcc_request.sock.send(send_msg)
        rev = dcc_request.rev_message(int(time_out))
        result = rev.get_result(dict_avps_code_map, avp_dict_map)
        # logger.info("[recv CCA]: {}".format(result))
        logger.info(f"[recv CCA]: \n{json.dumps(result, indent=4)}")
        print(f"[recv CCA]: \n{json.dumps(result, indent=4)}")
        if check_dict:
            err_code_list = self.check_result(result, check_dict)
            if err_code_list:
                logger.error(err_code_list)
                raise Exception("返回值和检验值存在不符情况，请查看日志！")
        return result

    def check_result(self, result, check_dict):
        # 校验结果
        err_code_list = []
        for cd in check_dict:
            if check_dict[cd] == NON_EXISTENT:
                # 字段不能在结果中出现
                if cd in result:
                    err_code_list.append("{}不该在结果中出现！".format(cd))
            else:
                if cd not in result:
                    err_code_list.append("{}结果中应该包含此avp！".format(cd))
                else:
                    value = check_dict[cd]
                    type_value = type(value)
                    if type_value == dict:
                        err_code_list += self.check_result(result[cd], value)
                    elif type_value == list:
                        for v in value:
                            type_v = type(v)
                            if type_v == dict:
                                try:
                                    v_index = value.index(v)
                                    child_result = result[cd][v_index]
                                    err_code_list += self.check_result(child_result, v)
                                except:
                                    err_code_list.append("{}结果中avp值不正确！".format(v))
                    else:
                        try:
                            f_value = float(value)
                            if f_value != float(result[cd]):
                                err_code_list.append("{}结果中avp值不正确！".format(cd))
                        except:
                            if value != type_value(result[cd]):
                                err_code_list.append("{}结果中avp值不正确！".format(cd))
        return err_code_list

    def remove_bracket(self, key):
        start_index = -1
        for avpfile_starts_string in AVPFILE_STARTS_STRING:
            try:
                key_index = key.index(avpfile_starts_string)
                if key_index > start_index:
                    return key[key_index + 1:-1]
            except:
                continue
        return key

    def __get_avpfile_text(self, avp_type, avpfile_root_path):
        project_code = FwVariables.get_dcc_project_code()
        avpfile_name = "avpfile-" + avp_type.lower() + "-" + project_code + ".ini"

        avpfile_path = os.path.join(avpfile_root_path, avpfile_name)
        with open(avpfile_path, "r") as f:
            result = f.readlines()
        return result

    def transfor_avpfile_to_json(self, olc_link_name, avp_type):
        # 根据avp_type和config中的project_code，生成avpfile文件名
        # if platform.system().lower() == 'windows':
        #     avpfile_root_path = "D:\\code\\gitea_cloudtest_library\\Case\\avpfile"
        # else:
        avpfile_root_path = os.path.join(os.getcwd() + os.sep + "Case",
                                         FwVariables.get_dcc_avpfile_path(olc_link_name))
        result = self.__get_avpfile_text(avp_type, avpfile_root_path)
        yml_str = ""
        result = result[1:]
        for line in result:
            line_split = line.split()
            key = line_split[0]
            new_key = self.remove_bracket(key)
            replace_index = line.index(key) + len(key)
            new_line = line[:replace_index] + ":\n"
            new_line = new_line.replace(key, new_key)
            yml_str += new_line
        # 转换成yml文件读取
        tmp_yml_path = os.path.join(avpfile_root_path, str(uuid.uuid4()) + ".yml")
        with open(tmp_yml_path, "w") as f:
            f.write(yml_str)
        with open(tmp_yml_path, encoding="utf-8") as f:
            transform_dict = yaml.full_load(f.read())
        os.remove(tmp_yml_path)
        return transform_dict


dccOperate = DccOperate()

if __name__ == '__main__':
    # with DccRequest2("OLC380") as dcc:
    #     dcc.send_dcc_message_with_response("00232423", "1")
    # dcc = DccRequest()
    # dcc.send_dcc_message_with_response("00232423", "1")
    # logger.info("%s" % msg)
    # req.connect()
    # req.login()
    # dpr = create_DPR()

    # print(dccOperate.transfor_avpfile_to_json("ccr"))
    # pass
    CCR_1 = {
        'Session-Id': 'OCS9s-08613850010709-20220922133409-5058', 'Origin-Host': FwVariables.get_origin_host(),
        'Origin-Realm': 'chinatelecom.com', 'Destination-Realm': 'chinatelecom.com',
        'Service-Context-Id': 'version1.ccg@chinatelecom.com',
        'CC-Request-Type': '1',
        'CC-Request-Number': '0',
        'Subscription-Id': [{'Subscription-Id-Type': '0', 'Subscription-Id-Data': '08613850010709'}],
        'Service-Identifier': '7',
        'Service-Information': {'PS-Information': {'3GPP-Charging-Id': '08613850010709', 'Called-Station-Id': '123'}},
        'MSCC': [{'Rating-Group': '2'}]
    }
    CCR_E = {
        "Session-Id": "OCS9s-08613850010709-20220922133409-5058",
        "Origin-Host": FwVariables.get_origin_host(),
        "Origin-Realm": "chinatelecom.com",
        "Destination-Realm": "chinatelecom.com",
        "Service-Context-Id": "version1.ccg@chinatelecom.com",
        "CC-Request-Type": "3",
        "CC-Request-Number": "2",
        "Subscription-Id": [{
            "Subscription-Id-Type": "0",
            "Subscription-Id-Data": "08613850010709"
        }],
        "MSCC": [{
            "Rating-Group": "2",
            "Reporting-Reason": "2",
            "Used-Service-Unit": [{
                "CC-Time": "600"
            }]
        }],
        "Service-Information": {
            "PS-Information": {
                "3GPP-Charging-Id": "08613850010709",
                "Called-Station-Id": "123",
                "3GPP-IMSI-MCC-MNC": "12345"
            }
        }
    }

    CCR_U1 = {
        "Session-Id": "OCS9s-08613850010709-20220922133409-5058",
        "Origin-Host": FwVariables.get_origin_host(),
        "Origin-Realm": "chinatelecom.com",
        "Destination-Realm": "chinatelecom.com",
        "Service-Context-Id": "version1.ccg@chinatelecom.com",
        "CC-Request-Type": "2",
        "CC-Request-Number": "1",
        "Subscription-Id": [{
            "Subscription-Id-Type": "0",
            "Subscription-Id-Data": "08613850010709"
        }],
        "Service-Identifier": "7",
        "MSCC": [{
            "Rating-Group": "2",
            "Requested-Service-Unit": {
                "CC-Time": "0"
            }
        }],
        "Service-Information": {
            "PS-Information": {
                "3GPP-Charging-Id": "08613850010709",
                "Called-Station-Id": "123",
                "3GPP-IMSI-MCC-MNC": "12345"
            }
        }
    }

    ccr_u2 = {
        "Session-Id": "OCS9s-08613850010709-20220922133409-5058",
        "Origin-Host": FwVariables.get_origin_host(),
        "Origin-Realm": "chinatelecom.com",
        "Destination-Realm": "chinatelecom.com",
        "Service-Context-Id": "version1.ccg@chinatelecom.com",
        "CC-Request-Type": "2",
        "CC-Request-Number": "2",
        "Subscription-Id": [{
            "Subscription-Id-Type": "0",
            "Subscription-Id-Data": "08613850010709"
        }],
        "MSCC": [{
            "Rating-Group": "2",
            "Reporting-Reason": "50",
            "Used-Service-Unit": [{
                "CC-Time": "600"
            }],
            "Requested-Service-Unit": {
                "CC-Time": "0"
            }
        }],
        "Service-Information": {
            "PS-Information": {
                "3GPP-Charging-Id": "08613850010709",
                "Called-Station-Id": "123",
                "3GPP-IMSI-MCC-MNC": "12345"
            }
        }
    }

    CCR_PRO = {
        "Session-Id": "OCS-63982123979-20221027161245-943980-9956",
        "Origin-Host": FwVariables.get_origin_host(),
        "Destination-Realm": "chinatelecom.com",
        "Service-Context-Id": "version1.in@chinatelecom.com",
        "CC-Request-Type": "1",
        "CC-Request-Number": "0",
        "Subscription-Id": [
            {
                "Subscription-Id-Type": "0",
                "Subscription-Id-Data": "63982123979"
            }
        ],
        "Service-Information": {
            "IN-Information": {
                "Calling-Party-Number": "63982123979",
                "Called-Party-Number": "13849654789",
                "Service-Key": "1",
                "Calling-Vlr-Number": "0",
                "Calling-CellID-Or-SAI": "0",
                "Calling-LAI": "0",
                "Called-Vlr-Number": "0",
                "Called-CellID-Or-SAI": "0",
                "Called-LAI": "0",
                "Bearer-Capability": "30",
                "EventType-BCSM": "2",
                "IMSI": "624587956123",
                "Call-Reference-Number": "123456"
            }
        },
        "MSCC": [
            {
                "Rating-Group": "1",
                "Requested-Service-Unit": ""
            }
        ]
    }

    ccr_2 = {
        "Session-Id": "OCS-63982123979-20221122164509-550206-5170",
        "Destination-Realm": "chinatelecom.com",
        "Service-Context-Id": "version1.in@chinatelecom.com",
        "CC-Request-Type": "1",
        "CC-Request-Number": "0",
        "Subscription-Id": [
            {
                "Subscription-Id-Type": "0",
                "Subscription-Id-Data": "63982123979"
            }
        ],
        "EventType-BCSM": "2",
        "Service-Information": {
            "IN-Information": {
                "Calling-Party-Number": "63982123979",
                "Called-Party-Number": "13849654789",
                "Service-Key": "1",
                "Calling-Vlr-Number": "0",
                "Calling-CellID-Or-SAI": "0",
                "Calling-LAI": "0",
                "Called-Vlr-Number": "0",
                "Called-CellID-Or-SAI": "0",
                "Called-LAI": "0",
                "Bearer-Capability": "30",
                "EventType-BCSM": "2",
                "IMSI": "624587956123",
                "Call-Reference-Number": "123456"
            }
        },
        "MSCC": [
            {
                "Rating-Group": "1",
                "Requested-Service-Unit": ""
            }
        ],
        "Origin-Host": FwVariables.get_origin_host(),
        "Origin-Realm": "chinatelecom.com"
    }
    # result = dccOperate.send_dcc_message("OLC380", "CCR", CCR_1, None)
    # print(result)
    # result = dccOperate.send_dcc_message("OLC380", "CCR", CCR_E, None)
    # print(result)
    # result = dccOperate.send_dcc_message("OLC380", "CCR", CCR_U1, None)
    # print(result)
    # result = dccOperate.send_dcc_message("OLC380", "CCR", ccr_u2, None)
    # print(result)
    result = dccOperate.send_dcc_message("OLC380", "CCR", ccr_2, None)
    print(result)
